/**
	Pulsar engine. Core.
	Memory managment (release/retail versions)

	Copyright (C) 2013-2014 Dmitry Dolgov

	This program is free software; you can redistribute it and/or modify it under the terms of the
	GNU General Public License as published by the Free Software Foundation; either version 2 of
	the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
	without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
	See the GNU General Public License for more details.
*/

#define __PL_NO_MEMORY_MAPPING__

#include <pulsar/pulsar.h>
#include <pulsar/src/pulsar_intr.h>

namespace pl
{

void* _STUB_ZERO_ALLOCATION_ADDRESS = (void*)0x73FA2B11;
MemGlobalInfo _memGlobalInfo;

} // namespace pl

void pl::StartupFuncBase::setGlobalAlloc(IGlobalAlloc* iga)
{
	_memGlobalInfo.iGlobalAlloc = iga;
	return;
}

/**
	Set max blocks or allocation size
	Works with PL_MEMORY_DEBUG only
*/
void pl::StartupFuncBase::memDebugSetStressAlloc(pl_uint32 nMaxBlocks, pl_uint32 nMaxSize)
{
	_memGlobalInfo.nMaxAllowedBlocks = nMaxBlocks;
	_memGlobalInfo.nMaxAllowedBytes = nMaxSize;
	return;
}

/**
	Set footer size
	Works with PL_MEMORY_DEBUG only
*/
void pl::StartupFuncBase::memDebugSetOptions(pl_uint32 options, pl_uint16 footerSize)
{
	_memGlobalInfo.allocationOptions = options;
	_memGlobalInfo.footerSize = footerSize;
	return;
}

/**
	Allocate memory (release version)
*/
void* pl::mallocRtl(size_t size)
{
	if (size == 0)
	{
		switch (_memGlobalInfo.allocationOptions & MEMOPTIONS_ZEROALLOC_MASK)
		{
			case MEMOPTIONS_ZEROALLOC_DEFAULT:
				break;
			case MEMOPTIONS_ZEROALLOC_MALLOC:
				size++; // Required for non-MSVC compilers
				break;
			case MEMOPTIONS_ZEROALLOC_STUB:
				return _STUB_ZERO_ALLOCATION_ADDRESS;
			case MEMOPTIONS_ZEROALLOC_NULL:
				return NULL;
			case MEMOPTIONS_ZEROALLOC_BREAK:
				PL_MEM_ERROR2(NULL, size, MEMORY_ERROR_MALLOC_ZERO);
		}
	}
	if (_memGlobalInfo.fDisableMalloc)
	{
		PL_MEM_ERROR2(NULL, size, MEMORY_ERROR_ALLOC_DISABLED);
		return NULL;
	}

	void *p = NULL;

	if (_memGlobalInfo.iGlobalAlloc == NULL)
	{
		p = malloc(size);
	}
	else
	{
		p = _memGlobalInfo.iGlobalAlloc->onAlloc(size, 0);
	}

	if (p == NULL)
	{
		PL_MEM_ERROR2(NULL, size, MEMORY_ERROR_OUT);
		return NULL;
	}

	pl::IMemoryMonitor::_callOnAlloc(p, size, NULL, 0, NULL, BLOCK_TYPE_MALLOC);
	return p;
}

/**
	Allocate memory and zero it (release version)
*/
void* pl::callocRtl(size_t n, size_t size)
{
	void* p = mallocRtl(n*size);

	if (p != NULL && n*size > 0)
	{
		memset(p, 0, n*size);
	}
	return p;
}

/**
	Reallocate block (release version)
*/
void* pl::reallocRtl(void* p, size_t size)
{
#ifndef PL_MSC
	if (p == NULL)
	{
		return mallocRtl(size);
	}
	if (size == 0)
	{
		freeRtl(p);
		return NULL;
	}
#endif // PL_MSC

	void* pNew;

	if (_memGlobalInfo.iGlobalAlloc == NULL)
	{
		pNew = realloc(p, size);
	}
	else
	{
		pNew = _memGlobalInfo.iGlobalAlloc->onRealloc(p, size);
	}

	if (pNew == NULL)
	{
		return NULL;
	}
	pl::IMemoryMonitor::_callOnFree(p, 0, NULL, 0, NULL, BLOCK_TYPE_MALLOC);
	pl::IMemoryMonitor::_callOnAlloc(pNew, size, NULL, 0, NULL, BLOCK_TYPE_MALLOC);
	return pNew;
}

/**
	Free allocated memory (release version)
*/
void pl::freeRtl(void* p)
{
	if (p == NULL)
	{
		switch (_memGlobalInfo.allocationOptions & MEMOPTIONS_FREEMODE_MASK)
		{
			case MEMOPTIONS_FREEMODE_DEFAULT:
				break;
			case MEMOPTIONS_FREEMODE_NULLSKIP:
				return;
			case MEMOPTIONS_FREEMODE_NULLBREAK:
				PL_MEM_ERROR2(p, 0, MEMORY_ERROR_FREE_NULL);
		}
	}

	if (_memGlobalInfo.fDisableMalloc)
	{
		PL_MEM_ERROR2(p, 0, MEMORY_ERROR_ALLOC_DISABLED);
		return;
	}

	if (p == _STUB_ZERO_ALLOCATION_ADDRESS)
	{
		// Stub allocation address was used
		return;
	}

	pl::IMemoryMonitor::_callOnFree(p, 0, NULL, 0, NULL, BLOCK_TYPE_MALLOC);

	if (_memGlobalInfo.iGlobalAlloc == NULL)
	{
		free(p);
	}
	else
	{
		_memGlobalInfo.iGlobalAlloc->onFree(p);
	}
	return;
}

/**
	Duplicate a string (release version)
*/
char* pl::strdupRtl(const char* str)
{
	if (str == NULL)
	{
		return NULL;
	}

	char* p;
	size_t len = strlen(str)+1;

	if (_memGlobalInfo.iGlobalAlloc == NULL)
	{
		p = strdup(str);
	}
	else
	{
		p = (char*)_memGlobalInfo.iGlobalAlloc->onAlloc(len, 0);
		if (p != NULL)
		{
			strcpy(p, str);
		}
	}

	if (p == NULL)
	{
		return NULL;
	}

	pl::IMemoryMonitor::_callOnAlloc(p, len, NULL, 0, NULL, BLOCK_TYPE_MALLOC);
	return p;
}

/**
	Allocate aligned memory (release version)
*/
void* pl::mallocAlignRtl(int align, size_t size)
{
	void* p;

	if (size == 0)
	{
		switch (_memGlobalInfo.allocationOptions & MEMOPTIONS_ZEROALLOC_MASK)
		{
			case MEMOPTIONS_ZEROALLOC_DEFAULT:
				break;
			case MEMOPTIONS_ZEROALLOC_MALLOC:
				size++; // Required for non-MSVC compilers
				break;
			case MEMOPTIONS_ZEROALLOC_STUB:
				return _STUB_ZERO_ALLOCATION_ADDRESS;
			case MEMOPTIONS_ZEROALLOC_NULL:
				return NULL;
			case MEMOPTIONS_ZEROALLOC_BREAK:
				PL_MEM_ERROR2(NULL, size, MEMORY_ERROR_MALLOC_ZERO);
		}
	}
	if (_memGlobalInfo.fDisableMalloc)
	{
		PL_MEM_ERROR2(NULL, size, MEMORY_ERROR_ALLOC_DISABLED);
		return NULL;
	}

	if (_memGlobalInfo.iGlobalAlloc == NULL)
	{
#if defined(PL_MSC)
		p = _aligned_malloc(size, align);
#elif defined(PL_GCC)
		p = alignedMallocGcc(size, align);
#else
#error "Wrong compiler"
#endif
	}
	else
	{
		p = _memGlobalInfo.iGlobalAlloc->onAlloc(size, align);
	}

	if (p == NULL)
	{
		PL_MEM_ERROR2(NULL, size, MEMORY_ERROR_OUT);
		return NULL;
	}

	pl::IMemoryMonitor::_callOnAlloc(p, size, NULL, 0, NULL, BLOCK_TYPE_MALLOC_ALIGN);
	return p;
}

/**
	Free aligned memory (release version)
*/
void pl::freeAlignRtl(void* p)
{
	if (_memGlobalInfo.fDisableMalloc)
	{
		PL_MEM_ERROR2(p, 0, MEMORY_ERROR_ALLOC_DISABLED);
		return;
	}
	if (p == _STUB_ZERO_ALLOCATION_ADDRESS)
	{
		// Stub allocation address was used
		return;
	}

	pl::IMemoryMonitor::_callOnFree(p, 0, NULL, 0, NULL, BLOCK_TYPE_MALLOC_ALIGN);

	if (_memGlobalInfo.iGlobalAlloc == NULL)
	{
#if defined(PL_MSC)
		_aligned_free(p);
#elif defined(PL_GCC)
		alignedFreeGcc(p);
#else
#error "Wrong compiler"
#endif
	}
	else
	{
		_memGlobalInfo.iGlobalAlloc->onFree(p);
	}
	return;
}

/**
	Initialize memory manager.
	Internal function - called automatically
*/
bool pl::initMemoryManager()
{
	static pl_uint8 bufferCs[RESERVED_SIZE + 2*sizeof(void*)];

	PL_STATIC_ASSERT(sizeof(_MemHeader) % 16 == 0);

	_memGlobalInfo.footerSize = max<pl_uint16>(4, _memGlobalInfo.footerSize);
#if defined(PL_WINDOWS) || defined(PL_LINUX)
	_memGlobalInfo.staticAllocAlignment = 16;
#elif defined(PL_WINDOWS64) || defined(PL_LINUX64)
	_memGlobalInfo.staticAllocAlignment = 32;
#else
	#error "Unknown target"
#endif

	_memGlobalInfo.pDebugCs = new(bufferCs) CriticalSection;
	_memGlobalInfo.fEnableThreadCheck = true;
	return true;
}

/**
	Shutdown memory manager.
	Internal function - called automatically
*/
void pl::termMemoryManager()
{
	CriticalSection *pSectionPtr = _memGlobalInfo.pDebugCs;
	_memGlobalInfo.pDebugCs = NULL;

	if (pSectionPtr != NULL)
	{
		// Use direct dtor because critical section was initialized with placement new
		pSectionPtr->~CriticalSection();
	}
	return;
}

//
// End of file 'pulsar_memorymng.cpp'
//
